package com.xpalive.log;

import ch.qos.logback.classic.sift.ContextBasedDiscriminator;
import ch.qos.logback.classic.spi.ILoggingEvent;

//https://segmentfault.com/a/1190000016204970?utm_source=tag-newest

/**
 * <pre>
 * 优化logback：增加无需同步的SiftAppender
 *
 * logback Appender有两个最基础的抽象类，一个是同步AppenderBase，
 * 一个是不同步的UnsynchronizedAppenderBase，这两个类功能一样，
 * 只是doAppender方法的同步策略不一样（Synchronize VS ThreadLocal）。
 * 那么SiftingAppender为什么继承了AppenderBase而不是UnsynchronizedAppenderBase呢？
 * 分析原因应该是SiftingAppender作为Appender的代理集合，
 * 它即可能包含了继承自UnsynchronizedAppenderBase的OutputStreamAppender
 * （FileAppender的基类，自行实现底层同步，doAppend方法未同步），
 * 也可能包含了继承AppenderBase的SocketAppender类（doAppend方法同步），
 * 为防止出现线程安全问题，它直接在自身的doAppend方法上进行了同步。
 *
 * 在项目组实际使用时，项目组只需要分线程写文件日志，这意味这它使用的最终FileAppender应该是线程安全，
 * 完全独立的。故我们尝试在logback新增继承于UnsynchronizedAppenderBase的ParrelSiftingAppender步骤如下：
 *
 * logback core中增加继承UnsynchronizedAppenderBase的UnsynchronizedSiftingAppenderBase
 * logback classic中增加继承UnsynchronizedSiftingAppenderBase的ParrelSiftingAppender
 * logback classic中SiftAction注册增加ParrelSiftingAppender的工厂注册
 *
 * 修改完成后测试发现果然性能提升了5倍左右，CPU资源利用率接近饱和，应该基本达到效果了，JMC分析应该是没有竞争了把，但是发现新的竞争方法出现了：
 * Appender<E> appender = appenderTracker.getOrCreate(discriminatingValue, timestamp);
 *
 * 最终改进：使用ConcurrentHashMap替换LinkedHashMap
 *
 * 原来在SiftingAppender内部使用了LinkedHashMap作为LRU Cache来管理Appender，
 * 会定期移除。由于LinkedHashMap不是线程安全的，故在getOrCreate方法上增加了Synchronized同步操作，
 * 造成竞争等待。
 *
 * 结合业务场景，这里完全可以使用ConcurrentHashMap作为Appender的缓存，
 * ConcurrentHashMap读写锁分离，并且key值分区上锁，在多读少写的情况下，
 * 有很高的并发性能。并且真正生成的日志Appender也并不多（100个左右），
 * 定时清理完全也不会出现内存溢出问题。
 *
 * 开始动手修改appenderTracker步骤如下：
 *
 * logback core中增加AbstractConcurrentMapComponentTracker
 * logback core中增加ConcurrentMapAppenderTracker继承
 * AbstractConcurrentMapComponentTracker
 *
 * 修改UnsynchronizedSiftingAppenderBase使用ConcurrentMapAppenderTracker进行Appender管理
 *
 * 最终性能测试结果：在资源相同的情况下，优化后比使用LinkedHashMap性能提高一倍。
 * 至此，整个logback多线程调优结束，通过充分优化同步竞争的方式，
 * 最终使得分线程记录日志的性能比最原始的多线程写同一文件提高了10倍
 * （SiftAppender去锁提高到5倍，Map替换提高2倍）！
 * </pre>
 */
public class XThreadDiscriminator extends ContextBasedDiscriminator {

    private String KEY = "threadName";

    /**
     * Return the name of the current context name as found in the logging event.
     */
    public String getDiscriminatingValue(ILoggingEvent event) {
        return event.getThreadName();
    }

    public String getDefaultValue() {
        return KEY;
    }

    public String getKey() {
        return KEY;
    }

    public void setKey(String key) {
        this.KEY = key;
    }
}
